#!/usr/bin/python3
# coding=utf-8
"""
# licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#     http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Author: YangYunYi
# Create: 2020-11-04
"""
import networkx as nx
import matplotlib.pyplot as plt
import autoimport.logmodule.logger as logger
import queue

this_logger = logger.init('getrequires')
logger.add_file_handler(this_logger, "getrequires", "getrequires")


class PkgVertex:
    """
    节点对象
    """

    def __init__(self, package):
        """
        构造函数
        :param package: 包名
        """
        # 软件包名
        self.pkg = package
        # 记录这个点的入度
        self.in_degree = 0
        # 记录这个点的临接点
        self.connected_to = set()

    def add_neighbor(self, nbr):
        """
        添加邻接点
        :param nbr: 本节点的相邻节点
        :return: False-节点已经存在;True-节点添加成功
        """
        if nbr.pkg in self.connected_to:
            return False
        self.connected_to.add(nbr.pkg)
        return True

    def add_in_degree(self):
        """
        入度加1，被依赖的数量加1
        :return: 无
        """
        self.in_degree += 1

    def __str__(self):
        """
        重载str函数
        :return: 无
        """
        return "{0}-{1}-->{2}".format(str(self.pkg.name), str(self.in_degree),
                                      str([nbr.pkg for nbr in self.connected_to]))


class PkgGraph:
    """
    图对象
    """

    def __init__(self):
        """
        构造函数
        """
        # 记录点
        self.vertex_list = {}
        # 记录边
        self.edge_list = set()

    def add_vertex(self, pkg_name):
        """
        添加节点
        :param pkg_name: 软件包名
        :return: 无
        """
        if pkg_name not in self.vertex_list.keys():
            self.vertex_list[pkg_name] = PkgVertex(pkg_name)

    def get_vertex(self, key):
        """
        获取节点
        :param key: 软件包名
        :return: 图的顶点
        """
        vertex = self.vertex_list.get(key)
        return vertex

    def add_edge(self, from_vertex_name, to_vertex_name):
        """
        添加边
        :param from_vertex_name: 被依赖节点
        :param to_vertex_name: 依赖节点
        :return: 无
        """
        self.add_vertex(from_vertex_name)
        self.add_vertex(to_vertex_name)
        from_vertex, to_vertex = self.get_vertex(from_vertex_name), self.get_vertex(to_vertex_name)
        if from_vertex.add_neighbor(to_vertex):
            to_vertex.add_in_degree()
            self.edge_list.add((to_vertex.pkg, from_vertex.pkg))

    def get_vertices(self):
        """
        获取顶点列表
        :return: 节点包名列表
        """
        return self.vertex_list.keys()

    def __iter__(self):
        """
        重载迭代器
        """
        return iter(self.vertex_list.values())

    def print(self, file_path):
        """
        将Graph中的邻接关系与边打印在文件中
        :param file_path: 打印文件名
        :return: 无
        """
        with open(file_path, 'w') as print_file:
            print('=====vertex(num:{})======'.format(str(len(self.vertex_list))), file=print_file)
            for vertex in self.vertex_list:
                print("vertex:{}[".format(vertex), end=' ', file=print_file)
                for item in self.vertex_list[vertex].connected_to:
                    print('{},'.format(item), end=' ', file=print_file)
                print(']\n', end='', file=print_file)
            print('=====edges(num:{})======'.format(str(len(self.edge_list))), file=print_file)
            for edge in self.edge_list:
                print('{0}->{1}'.format(edge[0], edge[1]), file=print_file)
        this_logger.info('Print graph to %s ok', file_path)

    def draw_graph(self, file_path):
        """
        绘制依赖图，并保存
        :param file_path: 图片保存路径与名称
        :return:
        """
        graph = nx.DiGraph()
        graph.add_edges_from(self.edge_list)
        nx.draw(graph, with_labels=True, node_size=100, font_size=9, width=0.5)
        plt.savefig(file_path)
        this_logger.info('Graph has saved in %s', file_path)
        plt.show()

    def topo_sort(self):
        """
        拓扑排序
        :return: 排序后的定点（软件包）列表
        """
        # 初始化所有点的入度为0
        in_degrees = dict((u.pkg, u.in_degree) for u in self.vertex_list.values())
        vertex_num = len(in_degrees)
        # 筛选入度为0的顶点
        zero_degrees = [vertex for vertex in in_degrees if in_degrees[vertex] == 0]
        this_logger.info('Graph topo_sort start. vertex num:%d, zero-degree vertex num:%d',
                         vertex_num, len(zero_degrees))
        ret_list = []
        while zero_degrees:
            vertex_name = zero_degrees.pop(0)
            ret_list.append(vertex_name)
            vertex_pkg = self.get_vertex(vertex_name)
            for nbr in vertex_pkg.connected_to:
                in_degrees[nbr] -= 1
                if in_degrees[nbr] == 0:
                    zero_degrees.append(nbr)

        ret_type = False
        if len(ret_list) == vertex_num:
            this_logger.info('Graph topo_sort succeed with %d num', vertex_num)
            ret_type = True
        else:
            this_logger.error('Graph topo_sort failed with %d num. '
                              'Please check whether there is a ring in the directed graph',
                              len(ret_list))
        return ret_type, ret_list
